using System.Linq;
using Generator.Model;

namespace Generator.Renderer.Internal;

internal static class OpaqueUntypedRecordHandle
{
    public static string Render(GirModel.Record record)
    {
        if (!record.Functions.Any() && !record.Methods.Any())
            return EmptyHandle(record);

        return StandardHandle(record);
    }

    private static string EmptyHandle(GirModel.Record record)
    {
        var typeName = Model.OpaqueUntypedRecord.GetInternalHandle(record);
        var unownedHandleTypeName = Model.OpaqueUntypedRecord.GetInternalUnownedHandle(record);
        var ownedHandleTypeName = Model.OpaqueUntypedRecord.GetInternalOwnedHandle(record);

        return $@"using System;
using GObject;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;

#nullable enable

namespace {Namespace.GetInternalName(record.Namespace)};

// AUTOGENERATED FILE - DO NOT MODIFY

{PlatformSupportAttribute.Render(record as GirModel.PlatformDependent)}
public abstract class {typeName} : SafeHandle
{{
    public sealed override bool IsInvalid => handle == IntPtr.Zero;

    protected {typeName}(bool ownsHandle) : base(IntPtr.Zero, ownsHandle) {{ }}

    public {ownedHandleTypeName} OwnedCopy() => throw new NotSupportedException(""Can't create a copy of this handle"");
    public {unownedHandleTypeName} UnownedCopy() => throw new NotSupportedException(""Can't create a copy of this handle"");

    public bool Equals({typeName}? other)
    {{
        if (ReferenceEquals(null, other))
            return false;

        if (ReferenceEquals(this, other))
            return true;

        return handle.Equals(other.handle);
    }}

    public override bool Equals(object? obj)
    {{
        return ReferenceEquals(this, obj) || obj is {typeName} other && Equals(other);
    }}

    public override int GetHashCode()
    {{
        return handle.GetHashCode();
    }}
}}

public class {unownedHandleTypeName} : {typeName}
{{
    private static {unownedHandleTypeName}? nullHandle;
    public static {unownedHandleTypeName} NullHandle => nullHandle ??= new {unownedHandleTypeName}();

    /// <summary>
    /// Creates a new instance of {unownedHandleTypeName}. Used automatically by PInvoke.
    /// </summary>
    internal {unownedHandleTypeName}() : base(false) {{ }}

    /// <summary>
    /// Creates a new instance of {unownedHandleTypeName}. Assumes that the given pointer is unowned by the runtime.
    /// </summary>
    public {unownedHandleTypeName}(IntPtr ptr) : base(false)
    {{
        SetHandle(ptr);
    }}

    protected override bool ReleaseHandle()
    {{
        throw new System.Exception(""UnownedHandle must not be freed"");
    }}
}}

public class {ownedHandleTypeName} : {typeName}
{{
    /// <summary>
    /// Creates a new instance of {ownedHandleTypeName}. Used automatically by PInvoke.
    /// </summary>
    internal {ownedHandleTypeName}() : base(true) {{ }}

    /// <summary>
    /// Creates a new instance of {ownedHandleTypeName}. Assumes that the given pointer is owned by the runtime.
    /// </summary>
    public {ownedHandleTypeName}(IntPtr ptr) : base(true)
    {{
        SetHandle(ptr);
    }}

    /// <summary>
    /// Create a {ownedHandleTypeName} from a pointer that is assumed unowned.
    /// </summary>
    /// <param name=""ptr"">A pointer to a {record.Name} which is not owned by the runtime.</param>
    /// <returns>A {ownedHandleTypeName}</returns>
    public static {ownedHandleTypeName} FromUnowned(IntPtr ptr) => throw new NotSupportedException(""Can't create a copy of this handle"");

    protected override bool ReleaseHandle() => throw new NotSupportedException(""Can't free this handle"");
}}";
    }

    private static string RenderCopyFunctions(GirModel.Record record)
    {
        var unownedHandleTypeName = Model.OpaqueUntypedRecord.GetInternalUnownedHandle(record);
        var ownedHandleTypeName = Model.OpaqueUntypedRecord.GetInternalOwnedHandle(record);

        return record.CopyFunction is null || !Method.IsValidCopyFunction(record.CopyFunction)
            ? $"""
                public partial {ownedHandleTypeName} OwnedCopy();
                public partial {unownedHandleTypeName} UnownedCopy();
            """
            : $$"""
                [DllImport(ImportResolver.Library, EntryPoint = "{{record.CopyFunction}}")]
                protected static extern IntPtr Copy(IntPtr handle);

                public {{ownedHandleTypeName}} OwnedCopy()
                {
                  return new {{ownedHandleTypeName}}(Copy(handle));
                }

                public {{unownedHandleTypeName}} UnownedCopy()
                {
                  return new {{unownedHandleTypeName}}(Copy(handle));
                }
            """;
    }

    private static string RenderFromUnowned(GirModel.Record record)
    {
        var ownedHandleTypeName = Model.OpaqueUntypedRecord.GetInternalOwnedHandle(record);

        return record.CopyFunction is null || !Method.IsValidCopyFunction(record.CopyFunction)
            ? $"""
                   /// <summary>
                   /// Create a {ownedHandleTypeName} from a pointer that is assumed unowned.
                   /// </summary>
                   /// <param name="ptr">A pointer to a {record.Name} which is not owned by the runtime.</param>
                   /// <returns>A {ownedHandleTypeName}</returns>
                   public static partial {ownedHandleTypeName} FromUnowned(IntPtr ptr);
               """
            : $$"""
                    public static {{ownedHandleTypeName}} FromUnowned(IntPtr ptr)
                    {
                        return new {{ownedHandleTypeName}}(Copy(ptr));
                    }
                """;
    }

    private static string RenderReleaseHandle(GirModel.Record record)
    {
        return record.FreeFunction is null || !Method.IsValidFreeFunction(record.FreeFunction)
            ? "protected override partial bool ReleaseHandle();"
            : $$"""
                    [DllImport(ImportResolver.Library, EntryPoint = "{{record.FreeFunction}}")]
                    private static extern void Free(IntPtr handle);
                    
                    protected override bool ReleaseHandle()
                    {
                        Free(handle);
                        return true;
                    }
                """;
    }

    private static string StandardHandle(GirModel.Record record)
    {
        var typeName = Model.OpaqueUntypedRecord.GetInternalHandle(record);
        var unownedHandleTypeName = Model.OpaqueUntypedRecord.GetInternalUnownedHandle(record);
        var ownedHandleTypeName = Model.OpaqueUntypedRecord.GetInternalOwnedHandle(record);

        return $@"using System;
using GObject;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;

#nullable enable

namespace {Namespace.GetInternalName(record.Namespace)};

// AUTOGENERATED FILE - DO NOT MODIFY

{PlatformSupportAttribute.Render(record as GirModel.PlatformDependent)}
public abstract partial class {typeName} : SafeHandle
{{
    public sealed override bool IsInvalid => handle == IntPtr.Zero;

    protected {typeName}(bool ownsHandle) : base(IntPtr.Zero, ownsHandle) {{ }}

    {RenderCopyFunctions(record)}
}}

public class {unownedHandleTypeName} : {typeName}
{{
    private static {unownedHandleTypeName}? nullHandle;
    public static {unownedHandleTypeName} NullHandle => nullHandle ??= new {unownedHandleTypeName}();

    /// <summary>
    /// Creates a new instance of {unownedHandleTypeName}. Used automatically by PInvoke.
    /// </summary>
    internal {unownedHandleTypeName}() : base(false) {{ }}

    /// <summary>
    /// Creates a new instance of {unownedHandleTypeName}. Assumes that the given pointer is unowned by the runtime.
    /// </summary>
    public {unownedHandleTypeName}(IntPtr ptr) : base(false)
    {{
        SetHandle(ptr);
    }}

    protected override bool ReleaseHandle()
    {{
        throw new System.Exception(""UnownedHandle must not be freed"");
    }}
}}

public partial class {ownedHandleTypeName} : {typeName}
{{
    /// <summary>
    /// Creates a new instance of {ownedHandleTypeName}. Used automatically by PInvoke.
    /// </summary>
    internal {ownedHandleTypeName}() : base(true) {{ }}

    /// <summary>
    /// Creates a new instance of {ownedHandleTypeName}. Assumes that the given pointer is owned by the runtime.
    /// </summary>
    public {ownedHandleTypeName}(IntPtr ptr) : base(true)
    {{
        SetHandle(ptr);
    }}
    {RenderFromUnowned(record)}
    {RenderReleaseHandle(record)}
}}";
    }
}
